﻿/*
   Copyright 2013 Dmitry Bratus

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Flower.Services.Data;
using Jurassic.Library;
using Jurassic;
using Flower.Directory.Host;
using Flower.Directory.Scripting.DirectoryEntries;
using Flower.Directory.Util;
using Flower.Services;

namespace Flower.Directory.Scripting.Functions.Processes
{
    internal class StartProcess : ClrFunction
    {
        public StartProcess(ScriptEngine engine)
            : base(engine.Function.InstancePrototype, "__startProcess", engine.Function.InstancePrototype)
        {
        }

        [JSCallFunction]
        public ObjectInstance Exec
        (
            string workflow, 
            ObjectInstance newProcess, 
            ObjectInstance initialState, 
            object localSets
        )
        {
            try
            {
                IDirectory dir = Engine.GetDirectory();

                var newProcessObj = newProcess as ProcessObject;
                if (newProcessObj == null)
                {
                    throw new JavaScriptException(Engine, "Error", "The newProcess argument is not ProcessObject.");
                }

                var initialStateObj = initialState as StateObject;
                if (initialStateObj == null)
                {
                    throw new JavaScriptException(Engine, "Error", "The initialState argument is not StateObject.");
                }

                var localSetsArr = localSets as ArrayInstance;
                var localSetsList =
                    (localSetsArr != null) ?
                        localSetsArr
                            .ElementValues
                            .Select(el => el as SetObject)
                            .Where(el => el != null)
                            .Select(el => el.Entry)
                            .ToArray() :
                        null;

                var result = Exec(dir, workflow, (Process)newProcessObj.Entry, (State)initialStateObj.Entry, localSetsList);

                return
                    (result != null) ?
                        MakeReturnObject((string)result["pid"], (string)result["processorName"]) :
                        MakeReturnObject(null, null);
            }
            catch (JavaScriptException)
            {
                throw;
            }
            catch (Exception ex)
            {
                throw new JavaScriptException(Engine, "Error", ex.Message, ex);
            }
        }

        private ObjectInstance MakeReturnObject(string pid, string processorName)
        {
            var result = Engine.Object.Construct();

            result.SetPropertyValue("pid", pid, true);
            result.SetPropertyValue("processorName", processorName, true);

            return result;
        }

        [ClrScript]
        public static Dictionary<string, object> Exec
        (
            IDirectory dir,
            string workflow, 
            Process newProcess, 
            State initialState, 
            IList<DirectoryEntry> localSets
        )
        {
            var processorNameResult = SelectProcessor.Exec(dir, workflow);
            if (processorNameResult == null)
            {
                return null;
            }

            string processorName = processorNameResult["processor"] as string;
            if (processorName == null)
            {
                return null;
            }

            string pendingFolderPath = string.Concat("/Processors/", processorName, "/Pending");
            DirectoryEntryInfo pendingFolder =
                dir.GetInfo(pendingFolderPath);
            DirectoryEntryInfo runningFolder =
                dir.GetInfo(string.Concat("/Processors/", processorName, "/Running"));

            if (pendingFolder == null || runningFolder == null)
            {
                return null;
            }

            ICollection<AccessControlEntry> waitersFolderAcl = pendingFolder.AccessControlList;
            var processAcl = new List<AccessControlEntry>();
            var stateAcl = new List<AccessControlEntry>();
            var processorRoles = new List<string>();

            foreach (AccessControlEntry acl in runningFolder.AccessControlList)
            {
                if (acl.Permission.HasFlag(Permission.CreateChildren))
                {
                    processAcl.Add
                    (
                        new AccessControlEntry
                        {
                            Role = acl.Role,
                            Permission = Permission.Read | Permission.Write | Permission.CreateChildren
                        }
                    );

                    stateAcl.Add
                    (
                        new AccessControlEntry
                        {
                            Role = acl.Role,
                            Permission = Permission.Read
                        }
                    );

                    processorRoles.Add(acl.Role);
                }
            }

            if (newProcess.AccessControlList.Count == 0)
            {
                newProcess.AccessControlList = processAcl;
            }

            string pid = dir.Save(pendingFolderPath, newProcess);

            if (initialState.AccessControlList.Count == 0)
            {
                initialState.AccessControlList = stateAcl;
            }

            dir.Save(pid, initialState);
            dir.Save
            (
                pid,
                new Folder
                {
                    Name = "Waiters",
                    AccessControlList = waitersFolderAcl
                }
            );
            dir.Save
            (
                workflow.Replace("/Workflows", "/Processes"),
                new Link
                {
                    Name = pid,
                    Target = pid
                }
            );

            if (localSets != null)
            {
                string localSetsParent = "/Sets/Local/" + pid;

                foreach (Set localSet in localSets)
                {
                    if (localSet.AccessControlList.Count == 0)
                    {
                        localSet.AccessControlList = new List<AccessControlEntry>();

                        foreach (AccessControlEntry acl in waitersFolderAcl)
                        {
                            var setAcl = new AccessControlEntry
                            {
                                Role = acl.Role,
                                Permission = acl.Permission
                            };

                            if (processorRoles.Contains(acl.Role))
                            {
                                setAcl.Permission |= Permission.Write;
                            }

                            localSet.AccessControlList.Add(setAcl);
                        }
                    }

                    dir.Save(localSetsParent, localSet);
                }
            }

            return new Dictionary<string, object>
            {
                { "pid", pid },
                { "processorName", processorName }
            };
        }
    }
}
